我來試試看直接套用我們的場景來解釋 GenServer 怎麼使用好了,
如果想要讀的話可以看 elixir 文件裡的 GenServer 或是 elixir school 的 OTP 章節
在我們伺服器上面,每一局新遊戲會是一個獨立的 process ,遊戲的狀態暫存與更新都在這裡進行,
另外我們也需要寫一系列的方法來操作每一個遊戲 process,
在這邊暫時稱 process 內的行為為 Server 端,
用來操作 process 的外層方法為 Client 。
這裡的 pid 是指 process identifier ,每次建立一個新的遊戲 process 都會回傳該 process 的 pid,
我們便可以把它存起來,有了 pid 就可以對他下命令,
像是圖裡面的出牌,便是使用新增 process 回傳的 pid 來對他下出牌要求
另外這邊要提的是,上圖的出牌是用 cast ,Client 發出要求之後就不會管了,繼續做別的事,
後面會盡量使用這個非同步的方式。
那查看遊戲狀態則是用 call, Client 在發出要求後會等待 Server 端回覆。
我們先建立 Server 端的好了,寫在上次建立的 game.ex 裡面
defmodule Game do
defstruct rounds: [], host_hand: [], guest_hand: [], round: 1, winner: nil
use GenServer
def init(status) do
{:ok, status}
end
def handle_call(:get_status, _from, status) do
{:reply, status, status}
end
end
要在 module 使用 GenServer 就要 use GenServer
(好像廢話
用了之後他會預期我們在 module 裡面定義一系列的方法,在收到來自 Client 端的請求時,
會依據請求執行相對應的方法,例如建立新的 process 就會用到 init 方法,
process 暫存初始狀態後回傳 pid 回去,
我們趕緊來 iex 試試
$ iex game.ex
iex(1)> GenServer.start_link(Game, %Game{})
{:ok, #PID<0.115.0>}
成功了,我們使用 start_link/2 方法開啟了在 Game module 的 game process,
並給他我們昨天設定好的 %Game{} struct
那我們再來來看看剛剛寫來查看遊戲狀態的 handle_call
def handle_call(:get_status, _from, status) do
{:reply, status, status}
end
handle_call/3 收三個變數 分別是事件名稱、要求來源 還有目前在 process 上的 遊戲狀態
而在方法裡面的要回傳的格式是 {:reply, 要回傳的東西, 更新後的狀態}
因為這次只需要回傳狀態,不需要更新狀態,所以第二三個是一樣的。
我們來 iex 使用看看
剛剛 start_link 的時候忘記存 pid 我們在建立一次
iex(2)> {:ok, game_pid} = GenServer.start_link(Game, %Game{})
{:ok, #PID<0.117.0>}
iex(3)> GenServer.call(game_pid, :get_status)
%Game{guest_hand: [], host_hand: [], round: 1, rounds: [], winner: nil}
成功了,我們得到了目前的遊戲狀態
當然每次都要呼叫 GenServer.call(game_pid, :get_status)
有點冗長,
我們明天來幫他包裝一下成為 Client 端的方法
在 elixir 裡面 =
是 pattern matching
他會盡量把右邊的東西比對到左邊
例如 [name, age] = ["John", 18]
這樣 name 的值就會是 "John"
如果雙方的架構不合 如 [name, age, gender] = ["John", 18]
或是左邊有固定的衝突值 如 [name, 19] = ["John", 18]
都會錯誤
這次的用法是 {:ok, game_pid} = {:ok, #PID<0.117.0>}
所以 game_pid 就會是 #PID<0.117.0>
elixir 定義方法是使用 def
接著方法名稱(小括號變數) do
與end
中間則是方法內容
方法必須要在 module 裡面
例如
defmodule MyMath do
def add(a, b) do
# #符號是行註解
# elixir 會回傳方法內的最後一行結果
# 在這個方法就是回傳 a + b
a + b
end
end
# 這樣子使用:
MyMath.add(13, 5)
一不小心就冒出超多新東西,對沒有看過 elixir 的朋友覺得滿不好意思的,
有任何問題就算是基本語法的都可以在下面問,我會盡量回答。